package bancosys.tec.rtm.impl.diagnostician;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.util.List;

import jmine.tec.persist.dbchangelog.dao.DatabaseChangeLogDAO;
import jmine.tec.persist.dbchangelog.domain.DatabaseChangeLog;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;

import bancosys.tec.persist.controller.TransactionalController;
import bancosys.tec.persist.hibernate.impl.annotations.AnnotationPartitionSessionFactory;
import bancosys.tec.rtm.diagnostician.AbstractDiagnostician;
import bancosys.tec.rtm.diagnostician.DiagnosisBean;

/**
 * Diagnosticador que recupera informações da conexão com a base de dados.
 * 
 * @author cesar
 */
public class DatabaseDiagnostician extends AbstractDiagnostician {

    private TransactionalController controller;

    /**
     * Construtor.
     * 
     * @param controller o controller.
     */
    public DatabaseDiagnostician(TransactionalController controller) {
        this.controller = controller;
    }

    /**
     * {@inheritDoc}
     */
    public String getName() {
        return "Informações sobre o Banco de Dados";
    }

    /**
     * {@inheritDoc}
     */
    protected boolean fillDiagnosisBean(DiagnosisBean bean) throws Exception {
        try {
            this.controller.startTransaction();
        } catch (Throwable t) {
            bean.addDescription("Erro tentando obter conexão: " + t.getMessage());
            return false;
        }

        try {
            Session session = this.controller.getSessionFactory().getCurrentSession();

            @SuppressWarnings("deprecation")
            Connection conn = session.connection();

            DatabaseMetaData metaData = conn.getMetaData();
            bean.addDescription("URL de conexão: " + metaData.getURL());
            bean.addDescription("username: " + metaData.getUserName());

            bean.addDescription("databaseProductName: " + metaData.getDatabaseProductName());
            bean.addDescription("databaseProductVersion: " + metaData.getDatabaseProductVersion());

            bean.addDescription("driverName: " + metaData.getDriverName());
            bean.addDescription("driverVersion: " + metaData.getDriverVersion());

            bean.addDescription("transaction isolation: " + metaData.getDefaultTransactionIsolation());

            ResultSet rs = metaData.getSchemas();
            try {
                while (rs.next()) {
                    bean.addDescription("Schema: " + rs.getString("TABLE_SCHEM"));
                }
            } finally {
                rs.close();
            }

            SessionFactory sf = this.controller.getSessionFactory();
            if (Proxy.isProxyClass(sf.getClass())) {
                InvocationHandler handler = Proxy.getInvocationHandler(sf);
                try {
                    Field field = handler.getClass().getDeclaredField("target");
                    field.setAccessible(true);
                    Object obj = field.get(handler);
                    if (obj instanceof SessionFactory) {
                        sf = (SessionFactory) obj;
                    }
                } catch (Throwable t) {
                    // o campo target não existe ou não está acessível. continuando....
                }
            }
            if (sf instanceof AnnotationPartitionSessionFactory) {
                AnnotationPartitionSessionFactory asf = (AnnotationPartitionSessionFactory) sf;

                bean.addDescription("");
                bean.addDescription("Configurações: ");
                Configuration cfg = asf.getConfiguration();
                for (Object key : cfg.getProperties().keySet()) {
                    if (((String) key).contains("hibernate")) {
                        String value = cfg.getProperty((String) key);
                        bean.addDescription("Propriedade " + key + ": " + value);
                    }
                }

                String defaultSchema = cfg.getProperty("hibernate.default_schema");
                if (defaultSchema != null) {
                    rs = metaData.getTables(null, defaultSchema, null, null);
                    try {
                        bean.addDescription("");
                        bean.addDescription("Tabelas do schema default (" + defaultSchema + "):");
                        while (rs.next()) {
                            String tableName = rs.getString("TABLE_NAME");
                            String tableType = rs.getString("TABLE_TYPE");
                            if (!tableName.contains("$")) {
                            	if (tableType.equalsIgnoreCase("TABLE") || tableType.equalsIgnoreCase("VIEW")) {
                                    bean.addDescription(tableType + " - " + tableName + "(" + this.getCount(session, defaultSchema, tableName)
                                            + ")" + this.getColumns(metaData, defaultSchema, tableName));
                            	} else {
                                    bean.addDescription(tableType + " - " + tableName);
                            	}
                            }
                        }
                    } finally {
                        rs.close();
                    }
                }
            }

            DatabaseChangeLogDAO databaseChangeLogDAO = this.controller.getDAOFactory().getDAOByClass(DatabaseChangeLogDAO.class);
            bean.addDescription("");
            bean.addDescription("Registros de alterações no banco de dados");
            bean.addDescription("");
            try {
                List<DatabaseChangeLog> dbLogs = databaseChangeLogDAO.findAllOrderedByTimestamp();
                bean.addDescription("Total de alterações: " + dbLogs.size());
                bean.addDescription("");
                for (DatabaseChangeLog dbLog : dbLogs) {
                    bean.addDescription("Alteração: " + dbLog.toString());
                }
                bean.addDescription("");
            } catch (Exception e) {
                bean.addDescription("Erro obtendo registros de alterações no banco de dados: " + e.getMessage());
            }
        } catch (Throwable t) {
            bean.addDescription("Erro obtendo meta dados da conexão: " + t.getMessage());
            return false;
        } finally {
            this.controller.rollback();
        }

        return true;
    }

    /**
     * Devolve o número de registros na tabela.
     * 
     * @param session a sessão do hibernate
     * @param schema o nome do schema
     * @param tableName o nome da tabela
     * @return o número de registros
     */
    private String getCount(Session session, String schema, String tableName) {
        try {
            String from = tableName;
            if (schema != null) {
                from = schema + "." + tableName;
            }
            List r = session.createSQLQuery("SELECT COUNT(*) FROM " + from).list();

            if (r == null || r.size() == 0) {
                return "";
            } else {
                return r.get(0).toString();
            }
        } catch (Throwable t) {
            return "";
        }
    }

    /**
     * Devolve as colunas de uma tabela.
     * 
     * @param metaData o meta dados de banco de dados
     * @param schema o nome do schema
     * @param tableName o nome da tabela
     * @return a descrição de todas as colunas
     */
    private String getColumns(DatabaseMetaData metaData, String schema, String tableName) {
        try {
            StringBuilder sb = new StringBuilder("[");
            String separator = "";
            ResultSet rs = metaData.getColumns(null, schema, tableName, null);
            try {
                while (rs.next()) {
                    sb.append(separator);
                    sb.append(rs.getString("COLUMN_NAME"));
                    sb.append(" ");
                    sb.append(rs.getString("TYPE_NAME"));
                    sb.append("(");
                    sb.append(rs.getString("COLUMN_SIZE"));
                    sb.append(",");
                    sb.append(rs.getString("DECIMAL_DIGITS"));
                    sb.append(")");
                    separator = ",";
                }
            } finally {
                rs.close();
            }
            sb.append("]");
            return sb.toString();
        } catch (Throwable t) {
            return "";
        }
    }
}
